#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <asm/string.h>
#include <asm/uaccess.h>
 
enum modes {_FIRST, DISTANCE, LUMA, IRLUMA, _LAST};
typedef int (*func_t)(void);
 
struct work_mode {
	func_t func;
	char * mode_name;
};
 
struct sfh7771_chip {
	struct i2c_client *client;
	atomic_t ref;
	enum modes mode;
};
 
struct sfh7771_chip  *g_sfh7771;
 
static int get_distance(void)
{
	int val = 0;
 
	val  =  i2c_smbus_read_byte_data(g_sfh7771->client, 0x44);
	val |= ( i2c_smbus_read_byte_data(g_sfh7771->client, 0x45) <<8);
 
	return val;
}
 
static int get_luma(void)
{
	int val = 0;
 
	val  =  i2c_smbus_read_byte_data(g_sfh7771->client, 0x46);
	val |= ( i2c_smbus_read_byte_data(g_sfh7771->client, 0x47) <<8);
 
	return val;
}
 
static int get_irluma(void)
{
	int val = 0;
 
	val  =  i2c_smbus_read_byte_data(g_sfh7771->client, 0x48);
	val |= ( i2c_smbus_read_byte_data(g_sfh7771->client, 0x49) <<8);
 
	return val;
}
 
struct work_mode works[] = {
	[DISTANCE]	= {get_distance, "distance"},
	[LUMA]		= {get_luma, "luma"},
	[IRLUMA]	= {get_irluma, "irluma"},
};
 
static int sfh7771_init_client(struct i2c_client *client)
{
	int ret;
 
	ret = i2c_smbus_write_byte_data(client, 0x40, 0x00); // No interrupt.
	ret = i2c_smbus_write_byte_data(client, 0x41, 0x00); // Data refreash rate. (0x06, 100ms)
	ret = i2c_smbus_write_byte_data(client, 0x42, 0x3f); // 25ma 0x3F
	ret = i2c_smbus_write_byte_data(client, 0x43, 0x02);
	ret = i2c_smbus_write_byte_data(client, 0x4A, 0x05); // 0x05
	ret = i2c_smbus_write_byte_data(client, 0x4B, 0x01);
	ret = i2c_smbus_write_byte_data(client, 0x4C, 0x00);	
 
	return ret;
}
 
static int sfh7771_open(struct inode *inode, struct file *file)
{
	int ret = 0;
 
	atomic_inc(&g_sfh7771->ref);
	if (atomic_read(&g_sfh7771->ref) == 1) {
		ret = i2c_smbus_write_byte_data(g_sfh7771->client, 0x41, 0x06);
		msleep(100);
	}
 
	return ret;
}
 
static int sfh7771_release(struct inode *inode, struct file *file)
{
	int ret = 0;
 
	atomic_dec(&g_sfh7771->ref);
	if (atomic_read(&g_sfh7771->ref) == 0) {
		ret = i2c_smbus_write_byte_data(g_sfh7771->client, 0x41, 0x00);
	}
 
	return ret;
}
 
static long sfh7771_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	int val;
	void *argp = (void *)arg;
 
	if ((cmd > _FIRST) && (cmd < _LAST)) {
		val = works[cmd].func();
	} else {
		val = -EINVAL;
	}
 
	ret = copy_to_user(argp, &val, sizeof(unsigned int));
	if (ret != 0) {
		printk("SFH7771 IOCTL ERROR!\n");
		ret = -EFAULT;
	}
 
	return ret;
}
 
static ssize_t sfh7771_read(struct file *filp, char __user *buf, size_t buf_len, loff_t *f_pos)
{
	int ret;
	int len;
	int val;
	char *name;
	unsigned char dat[64];
 
	if ((buf_len > 512) && (*f_pos > 0)) {
		*f_pos = 0;
		len = 0;
		goto read_err;
	}
 
	if ((g_sfh7771->mode > _FIRST) && (g_sfh7771->mode < _LAST)) {
		val = works[g_sfh7771->mode].func();
		name = works[g_sfh7771->mode].mode_name;
	} else {
		val = -1;
		name = "Not supported";
	}
 
	memset(dat, 0, 60);
	len = sprintf(dat, "%s:%6d\n", name, val);
 
	len = (len > buf_len) ? buf_len : len;
	ret = copy_to_user((void __user *)buf, dat, len);
	if (ret != 0) {
		ret = -EFAULT;
		goto read_err;
	}
 
	*f_pos = len;
 
read_err:
	return len;
}
 
static ssize_t sfh7771_write(struct file *filp, const char __user *dat, size_t dat_len, loff_t *f_pos)
{
	int ret;
	unsigned char *buf;
	int i;
 
	buf = (unsigned char *)kzalloc(dat_len, GFP_KERNEL);
	ret = copy_from_user(buf, dat, dat_len);
	if (ret != 0) {
		ret = -EFAULT;
		goto err_copy;
	}
 
	for (i = _FIRST + 1; i < _LAST; i++) {
		ret = strncmp(works[i].mode_name, buf, strlen(works[i].mode_name));
		if (ret == 0) {
			g_sfh7771->mode = i;
			ret = dat_len;
			break;
		}
	}
 
	if (i == _LAST) {
		ret = 0;
	}
 
	*f_pos = 0;
 
err_copy:
	return ret;
}
 
static const struct file_operations sfh7771_fops = {
	.owner		= THIS_MODULE,
	.open		= sfh7771_open,
	.release	= sfh7771_release,
	.read		= sfh7771_read,
	.write		= sfh7771_write,
	.unlocked_ioctl	= sfh7771_ioctl,
};
 
static struct miscdevice sfh7771_misc_driver = {
	.minor = MISC_DYNAMIC_MINOR,
	.name  = "irsensor",
	.fops  = &sfh7771_fops,
};
 
static int sfh7771_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct sfh7771_chip *chip;
	int res = 0;
	int err = 0;
 
	chip = kzalloc(sizeof *chip, GFP_KERNEL);
	if (chip == NULL) {
		return -ENOMEM;
	}
 
	i2c_set_clientdata(client, chip);
	chip->client = client;
 
	err = sfh7771_init_client(client);
	atomic_set(&chip->ref, 0);
	chip->mode = DISTANCE;
 
	err = misc_register(&sfh7771_misc_driver);
	if (err) {
		pr_err("sfh7771_probe: sfh7771_driver register failed\n");
		goto exit_misc_device_register_failed;
	}
 
	g_sfh7771 = chip;
 
	return res;
 
exit_misc_device_register_failed:
	return -1;
}
 
static int __exit sfh7771_remove(struct i2c_client *client)
{
	struct sfh7771_chip *chip = i2c_get_clientdata(client);
 
	misc_deregister(&sfh7771_misc_driver);
	kfree(chip);
 
	return 0;
}
 
static int sfh7771_suspend(struct i2c_client *client, pm_message_t mesg)
{
	i2c_smbus_write_byte_data(client, 0x41, 0x00);//Data refreash rate.
 
	return 0;
}
 
static int sfh7771_resume(struct i2c_client *client)
{
	sfh7771_init_client(client);
	msleep(5);
 
	i2c_smbus_write_byte_data(client, 0x41, 0x06);// Data refreash rate.
 
	return 0;
}
 
static const struct i2c_device_id sfh7771_id[] = {
	{"sfh7771", 0},
	{}
};
 
MODULE_DEVICE_TABLE(i2c, sfh7771_id);
 
static struct i2c_driver sfh7771_driver = {
	.probe		= sfh7771_probe,
	.remove		= sfh7771_remove,
	.suspend	= sfh7771_suspend,
	.resume		= sfh7771_resume,
	.id_table	= sfh7771_id,
	.driver		= {
		.name	= "sfh7771",
		.owner	= THIS_MODULE,
	},
};
 
static int __init sfh7771_init(void)
{
	int ret;
 
	ret = i2c_add_driver(&sfh7771_driver);
	if (ret < 0){
		printk(KERN_INFO "Add sfh7771 i2c driver failed\n");
		return -ENODEV;
	}
 
	return ret;
}
 
static void __exit sfh7771_exit(void)
{
	i2c_del_driver(&sfh7771_driver);
}
 
module_init(sfh7771_init);
module_exit(sfh7771_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yuyaowen");
